package com.baymax.test.scene.helper;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;

/**
 * 圆角裁切工具类。
 *
 * <p>原理：在 {@link android.view.View#draw(Canvas)} 方法中，首先绘制原来的内容，接着将对应的角进行裁切实现圆角。</p>
 *
 * @author yangliangwei
 * @date 2022-07-29
 */
public class RoundingHelper {
    /**
     * 当前圆角时需要裁切的形状。
     */
    private final Path path = new Path();
    /**
     * 用于裁切的 {@link Paint} 。
     */
    private final Paint clipPaint;
    /**
     * 用于裁切圆角的 {@link RectF} 。
     */
    private final RectF ovalRectF = new RectF();
    /**
     * 原始的绘制对象的绘制范围。
     */
    private final RectF drawRect = new RectF();

    /**
     * 左上角圆角半径。
     */
    private float topLeftRadius = 8f;
    /**
     * 右上角圆角半径。
     */
    private float topRightRadius = 8f;
    /**
     * 左下角圆角半径。
     */
    private float bottomLeftRadius = 8f;
    /**
     * 右下角圆角半径。
     */
    private float bottomRightRadius = 8f;

    public RoundingHelper() {
        clipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        clipPaint.setStyle(Paint.Style.FILL);
        clipPaint.setStrokeWidth(1);
        clipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
    }

    /**
     * 设置四个角的圆角半径。
     *
     * @param radius 圆角半径。
     */
    public void setRadius(float radius) {
        topLeftRadius = topRightRadius = bottomRightRadius = bottomLeftRadius = radius;
    }

    /**
     * 设置左上角圆角半径。
     *
     * @param radius 左上角圆角半径。
     */
    public void setTopLeftRadius(float radius) {
        this.topLeftRadius = radius;
    }

    /**
     * 设置右上角圆角半径。
     *
     * @param radius 右上角圆角半径。
     */
    public void setTopRightRadius(float radius) {
        this.topRightRadius = radius;
    }

    /**
     * 设置左下角圆角半径。
     *
     * @param radius 左下角圆角半径。
     */
    public void setBottomLeftRadius(float radius) {
        this.bottomLeftRadius = radius;
    }

    /**
     * 设置右下角圆角半径。
     *
     * @param radius 右下角圆角半径。
     */
    public void setBottomRightRadius(float radius) {
        this.bottomRightRadius = radius;
    }

    /**
     * 当前是否有任意一个角设置了圆角。
     *
     * @return 若有任意一个角设置了圆角则返回 true ；否则返回 false 。
     */
    private boolean hasRadius() {
        return topLeftRadius != 0 || topRightRadius != 0 || bottomLeftRadius != 0 | bottomRightRadius != 0;
    }

    /**
     * 裁减圆角。
     *
     * @param drawer {@link Drawer} 对象实例。
     * @param canvas 绘制使用的 {@link Canvas} 。
     */
    public void draw(Drawer drawer, Canvas canvas) {
        if (!hasRadius()) {
            drawer.draw0(canvas);
            return;
        }

        // 获取绘制范围。
        drawer.getDrawRect(drawRect);

        // 创建新 Layer 。
        int count = canvas.saveLayer(drawRect, null, Canvas.ALL_SAVE_FLAG);

        // 在新 Layer 上绘制原始内容。
        drawer.draw0(canvas);

        // 在新 Layer 上进行圆角裁减。
        clipTopLeft(canvas, topLeftRadius, drawRect);
        clipTopRight(canvas, topRightRadius, drawRect);
        clipBottomLeft(canvas, bottomLeftRadius, drawRect);
        clipBottomRight(canvas, bottomRightRadius, drawRect);
        // 还原 Layer 。
        canvas.restoreToCount(count);
    }

    /**
     * 裁切左上角圆角。
     *
     * @param canvas   绘制使用的 {@link Canvas} 。
     * @param radius   左上角圆角半径。
     * @param drawRect 绘制范围。
     */
    public void clipTopLeft(Canvas canvas, float radius, RectF drawRect) {
        if (radius <= 0) {
            return;
        }
        path.reset();
        path.moveTo(drawRect.left, drawRect.top + radius);
        path.rLineTo(-1, 0);
        path.rLineTo(0, -radius - 1);
        path.rLineTo(radius + 1, 0);
        path.rLineTo(0, 1);

        ovalRectF.set(drawRect.left, drawRect.top, drawRect.left + radius * 2, drawRect.top + radius * 2);
        path.arcTo(ovalRectF, 270, -90);
        path.close();
        canvas.drawPath(path, clipPaint);
    }

    /**
     * 裁切右上角圆角。
     *
     * @param canvas   绘制使用的 {@link Canvas} 。
     * @param radius   右上角圆角半径。
     * @param drawRect 绘制范围。
     */
    public void clipTopRight(Canvas canvas, float radius, RectF drawRect) {
        if (radius <= 0) {
            return;
        }
        path.reset();
        path.moveTo(drawRect.right, drawRect.top + radius);
        path.rLineTo(1, 0);
        path.rLineTo(0, -radius - 1);
        path.rLineTo(-radius - 1, 0);
        path.rLineTo(0, 1);

        ovalRectF.set(drawRect.right - 2 * radius, drawRect.top, drawRect.right, drawRect.top + radius * 2);
        path.arcTo(ovalRectF, 270, 90);
        path.close();
        canvas.drawPath(path, clipPaint);
    }

    /**
     * 裁切左下角圆角。
     *
     * @param canvas   绘制使用的 {@link Canvas} 。
     * @param radius   左下角圆角半径。
     * @param drawRect 绘制范围。
     */
    public void clipBottomLeft(Canvas canvas, float radius, RectF drawRect) {
        if (radius <= 0) {
            return;
        }
        path.reset();
        path.moveTo(drawRect.left, drawRect.bottom - radius);
        path.rLineTo(-1, 0);
        path.rLineTo(0, radius + 1);
        path.rLineTo(radius + 1, 0);
        path.rLineTo(0, -1);

        ovalRectF.set(drawRect.left, drawRect.bottom - radius * 2, drawRect.left + radius * 2, drawRect.bottom);
        path.arcTo(ovalRectF, 90, 90);
        path.close();
        canvas.drawPath(path, clipPaint);
    }

    /**
     * 裁切右下角圆角。
     *
     * @param canvas   绘制使用的 {@link Canvas} 。
     * @param radius   右下角圆角半径。
     * @param drawRect 绘制范围。
     */
    public void clipBottomRight(Canvas canvas, float radius, RectF drawRect) {
        if (radius <= 0) {
            return;
        }

        path.reset();
        path.moveTo(drawRect.right, drawRect.bottom - radius);
        path.rLineTo(1, 0);
        path.rLineTo(0, radius + 1);
        path.rLineTo(-radius - 1, 0);
        path.rLineTo(0, -1);

        ovalRectF.set(drawRect.right - radius * 2, drawRect.bottom - radius * 2, drawRect.right, drawRect.bottom);
        path.arcTo(ovalRectF, 90, -90);
        path.close();
        canvas.drawPath(path, clipPaint);
    }

    /**
     * 可以裁切的绘制对象接口。
     */
    public interface Drawer {
        /**
         * 获取绘制返回。
         *
         * @param rectF 绘制范围，需要将绘制范围设置给该参数。
         */
        void getDrawRect(RectF rectF);

        /**
         * 绘制内容。
         *
         * @param canvas 绘制使用的 {@link Canvas} 。
         */
        void draw0(Canvas canvas);
    }
}
